package tw.com.hitevision.whiteboard.android.widgets;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;
import tw.com.hitevision.whiteboard.android.R;

public class HexagonPalette extends View {
    private static final String TAG = HexagonPalette.class.getSimpleName();
    private static final float SQRT_3 = (float) Math.sqrt(3);
    private PointF detectPoint;
    private Paint paint;
    private PointF center;
    private List<Polygon> hexagons;
    private @ColorInt int strokeColor;
    private @ColorInt int frameColorLight;
    private @ColorInt int frameColorDark;
    private int strokeWidth;
    private HexagonLis hexagonLis;
    private Polygon selectedPolygon;
    private double hexagonArea;
    private boolean flatTop;
    private int numOfHexagon;
    private @ColorInt int defaultColor;
    private @ColorInt int[] colorCodes;

    public HexagonPalette(Context context) {
        super(context);
        init(context);
    }

    public HexagonPalette(Context context, AttributeSet attr) {
        super(context, attr);
        init(context);
    }

    @SuppressLint("ClickableViewAccessibility")
    private void init(Context context) {
        paint = new Paint();
        paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeJoin(Paint.Join.BEVEL);
        center = new PointF();
        strokeColor = ContextCompat.getColor(context, R.color.hexagon_frame_color);
        frameColorLight = ContextCompat.getColor(context, R.color.selected_frame_light_color);
        frameColorDark = ContextCompat.getColor(context, R.color.selected_frame_dark_color);
        hexagons = new ArrayList<>();
        strokeWidth = 2;
        flatTop = true;
        numOfHexagon = 0;
        selectedPolygon = null;
        detectPoint = new PointF();
        hexagonArea = 0;
//        currentColor = 0;
        defaultColor = 0;

        this.setOnTouchListener((v, event) -> {
            if (event.getAction() == MotionEvent.ACTION_POINTER_DOWN || event.getAction() == MotionEvent.ACTION_DOWN) {
                @ColorInt int result = selectColor(event.getX(), event.getY());
                if (hexagonLis != null && result != 0) {
                    hexagonLis.setColorFromHexagon(event, result);
                    invalidate();// call onDraw()
                }
//                    else if (hexagonLis != null) {
//                        clearSelectedColor();
//                        invalidate();
//                    }
            }
            return false;
        });

    }

    private static class Polygon {
        List<PointF> points;
        PointF center;
        float width;
        float x, y, z;
        @ColorInt
        int color;
        Path path;

        Polygon(float w, float centerX, float centerY) {
            center = new PointF(centerX, centerY);
            width = w;
            points = new ArrayList<>();
            x = 0;
            y = 0;
            z = 0;
            path = new Path();
        }

        void setColor(@ColorInt int c) {
            color = c;
        }

        void setHexagon(boolean flatTop) {
            float hexW, hexH;
            if (flatTop) {
                hexW = width;
                hexH = width * SQRT_3 / 2.0f;
                points.add(new PointF(center.x - hexW / 2, center.y - hexH));
                points.add(new PointF(center.x + hexW / 2, center.y - hexH));
                points.add(new PointF(center.x + hexW, center.y));
                points.add(new PointF(center.x + hexW / 2, center.y + hexH));
                points.add(new PointF(center.x - hexW / 2, center.y + hexH));
                points.add(new PointF(center.x - hexW, center.y));
            } else {
                hexW = width * SQRT_3 / 2.0f;
                hexH = width;
                points.add(new PointF(center.x, center.y - hexH));
                points.add(new PointF(center.x - hexW, center.y - hexH / 2));
                points.add(new PointF(center.x - hexW, center.y + hexH / 2));
                points.add(new PointF(center.x, center.y + hexH));
                points.add(new PointF(center.x + hexW, center.y + hexH / 2));
                points.add(new PointF(center.x + hexW, center.y - hexH / 2));
            }

            PointF last = points.get(points.size() - 1);
            path.moveTo(last.x, last.y);
            for (PointF i : points) {
                path.lineTo(i.x, i.y);
            }
        }
    }

    private @ColorInt
    int selectColor(float x, float y) {
        float offsetH;
        float unitH, tempH;
        int start, end;

        offsetH = (flatTop ? Math.abs(y - center.y) : Math.abs(x - center.x));
        unitH = hexagons.get(0).width;
        start = 0;
        end = numOfHexagon * 2 - 1 + 2 * (numOfHexagon * 2 - 2);
        tempH = unitH;
        for (int i = numOfHexagon * 2 - 1; i > numOfHexagon; i--) {
            if (offsetH <= tempH) {
                break;
            } else {
                tempH += 1.5f * unitH;
                start = end - 2 * (i - 1);
                end += 2 * (i - 2);
            }
        }

        for (int i = start; i < Math.min(end, hexagons.size()); i++) {
            Polygon p = hexagons.get(i);
            if (hitHexagon(x, y, p)) {
                selectedPolygon = p;
                return p.color;
            }
        }

        return 0;
    }

    private void clearSelectedColor() {
        if (selectedPolygon != null) {
            selectedPolygon = null;
        }
    }

    private boolean hitHexagon(float x, float y, Polygon p) {
        return getPolygonAreaWithPoint(p.points, x, y) <= (hexagonArea * 1.00001);
    }

    private float getPolygonAreaWithPoint(List<PointF> points, float x, float y) {
        float[] areas = new float[points.size()];

        for (int i = 0; i < points.size(); i++) {
            PointF a = points.get(i);
            PointF b = points.get((i + 1) % (points.size()));
            detectPoint.set(x, y);
            areas[i] = mathGetArea(a, b, detectPoint);
        }

        float totalArea = 0;

        for (float d : areas) {
            totalArea += d;
        }

        return totalArea;
    }

    public void setHexagonPalette(int hexagonWidth, int numOfHexagon, boolean flatTop, TypedArray codes) {
        if (hexagonWidth <= 0 || numOfHexagon <= 0 || codes == null || codes.length() <= 0) {
            return;
        }

        // set hexagon color picker center
        float paletteW = (flatTop ? (SQRT_3 * 6.5f * hexagonWidth) : (1.5f * 6f * hexagonWidth + hexagonWidth)) * 2.0f;

        // set Hexagon color picker size
        ViewGroup.LayoutParams params = getLayoutParams();
        params.width = (int) (paletteW + SQRT_3 * 3 * hexagonWidth);
        params.height = (int) (SQRT_3 * 13.5f * hexagonWidth);

        center.set(paletteW / 2.0f  + strokeWidth / 2.0f, params.height / 2.0f + strokeWidth / 2.0f);

        // get color codes from color.xml
        colorCodes = new int[codes.length()];
        for (int i = 0; i < codes.length(); i++) {
            colorCodes[i] = codes.getColor(i, 0);
        }

        // create hexagons palette
        this.flatTop = flatTop;
        this.numOfHexagon = numOfHexagon;

        Polygon p;
        float offsetW = (flatTop ? SQRT_3 * hexagonWidth : 1.5f * hexagonWidth);
        float offsetH = (flatTop ? 1.5f * hexagonWidth : SQRT_3 * hexagonWidth);

        if (flatTop) {
            for (int i = numOfHexagon * 2 - 1, j = 0; i >= numOfHexagon && j < numOfHexagon; i--, j++) {
                float x = center.x - (i - 1) * 0.5f * offsetW;
                float y = center.y - j * offsetH;
                for (int k = 0; k < i; k++) {
                    p = new Polygon(hexagonWidth, x + k * offsetW, y);
                    p.setHexagon(!flatTop);
                    hexagons.add(p);
                    p.setColor(colorCodes[hexagons.size() - 1]);
                }

                if (i < numOfHexagon * 2 - 1) {
                    y = center.y + j * offsetH;
                    for (int k = 0; k < i; k++) {
                        p = new Polygon(hexagonWidth, x + k * offsetW, y);
                        p.setHexagon(!flatTop);
                        hexagons.add(p);
                        p.setColor(colorCodes[hexagons.size() - 1]);
                    }
                }
            }
        } else {
            for (int i = numOfHexagon * 2 - 1, j = 0; i >= numOfHexagon && j < numOfHexagon; i--, j++) {
                float x = center.x - j * offsetW;
                float y = center.y - (i - 1) * 0.5f * offsetH;
                for (int k = 0; k < i; k++) {
                    p = new Polygon(hexagonWidth, x, y + k * offsetH);
                    p.setHexagon(!flatTop);
                    hexagons.add(p);
                    p.setColor(colorCodes[hexagons.size() - 1]);
                }

                if (i < numOfHexagon * 2 - 1) {
                    x = center.x + j * offsetW;
                    for (int k = 0; k < i; k++) {
                        p = new Polygon(hexagonWidth, x, y + k * offsetH);
                        p.setHexagon(!flatTop);
                        hexagons.add(p);
                        p.setColor(colorCodes[hexagons.size() - 1]);
                    }
                }
            }
        }

        // add side palette
        float sideCenterX, sideCenterY;
        sideCenterX = params.width - 2 * hexagonWidth / SQRT_3;
        sideCenterY = params.height / 2.0f + strokeWidth / 2.0f - 2 * offsetH;

        for (int i = 0; i < 5; i++) {
            p = new Polygon(hexagonWidth, sideCenterX, sideCenterY + i * offsetH);
            p.setHexagon(true);
            hexagons.add(p);
            p.setColor(colorCodes[hexagons.size() - 1]);
        }

        // update hexagon unit area
        Polygon first = hexagons.get(0);
        hexagonArea = getPolygonAreaWithPoint(first.points, first.center.x, first.center.y);

        setLayoutParams(params);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStrokeWidth(strokeWidth);

//        canvas.drawRect(0+edgeWidth,0+edgeWidth, getWidth()-edgeWidth, getHeight()-edgeWidth, paint);
        for (Polygon p : hexagons) {
            paint.setStrokeWidth(1);
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            paint.setColor(p.color);
            canvas.drawPath(p.path, paint);

            paint.setStrokeWidth(strokeWidth);
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(strokeColor);
            canvas.drawPath(p.path, paint);
        }

        if (selectedPolygon != null) {
            paint.setStrokeWidth(strokeWidth);
//            paint.setColor(frameColor);
            @ColorInt int c = selectedPolygon.color;
            if ((Color.red(c) + Color.green(c) + Color.blue(c)) > (255 * 3) / 2) {
                paint.setColor(frameColorDark);
            } else {
                paint.setColor(frameColorLight);
            }
            canvas.drawPath(selectedPolygon.path, paint);
        }
    }

    public @ColorInt
    int getSelectedColor() {
        return (selectedPolygon != null ? selectedPolygon.color : defaultColor);
    }

    public void setDefaultColor(@ColorInt int c) {
        defaultColor = c;
//        if (selectedPolygon!=null && selectedPolygon.color != c) {
        if (selectedPolygon == null || selectedPolygon.color != c) {
            for (Polygon p : hexagons) {
                if (p.color == c) {
                    selectedPolygon = p;
                    invalidate();
                    break;
                }
            }
        }
    }

    public void setHexagonLis(HexagonLis l) {
        hexagonLis = l;
    }

    private float pointsDistance(PointF A, PointF B) {
        return (float) (Math.sqrt((Math.pow(A.x - B.x, 2) + Math.pow(A.y - B.y, 2))));
    }

    private float mathGetArea(PointF A, PointF B, PointF C) {
        float a = pointsDistance(A, B);
        float b = pointsDistance(B, C);
        float c = pointsDistance(C, A);
        float s = (a + b + c) / 2;
        return (float) Math.sqrt(s * (s - a) * (s - b) * (s - c));
    }

    private void log(String str) {
        Log.d(TAG, str);
    }

    public interface HexagonLis {
        void setColorFromHexagon(MotionEvent e, @ColorInt int color);
    }
}
